using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Collections;
using System.Text;
using System.IO;

namespace NetFx.MSIL.Symbol
{
  #region Exceptions
  public class SymbolFileException : Exception
  {
    public SymbolFileException()
      : base()
    { }

    public SymbolFileException(string message, params object[] args)
      : base(String.Format(message, args))
    { }
  }
  #endregion
  #region MemoryStream
  public class ChunkMemoryStream : Stream
  {
    #region Fields
    protected int length;
    protected int real_length;
    protected int position;

    protected int chunk_size = 4096;
    protected ArrayList chunks = new ArrayList();
    #endregion
    #region Properties
    private struct Chunk
    {
      public readonly int Offset;
      public readonly int Length;
      public byte[] Buffer;

      public Chunk(int offset, int length)
      {
        this.Offset = offset;
        this.Length = length;
        this.Buffer = new Byte[length];
      }
    }

    public override long Position
    {
      get { return position; }

      set
      {
        if (value > length)
          throw new ArgumentOutOfRangeException();

        position = (int)value;
      }
    }

    public override long Length
    {
      get { return length; }
    }

    public override bool CanRead
    {
      get { return true; }
    }

    public override bool CanWrite
    {
      get { return true; }
    }

    public override bool CanSeek
    {
      get { return true; }
    }

    public override void SetLength(long new_length)
    {
      if (new_length < length)
        throw new ArgumentException();

      while (new_length >= real_length)
      {
        Chunk new_chunk = new Chunk(real_length, chunk_size);
        chunks.Add(new_chunk);
        real_length += chunk_size;
      }

      length = (int)new_length;
    }
    #endregion
    #region Constructors
    public override void Flush()
    { }
    #endregion
    #region Methods
    public override long Seek(long offset, SeekOrigin origin)
    {
      int ref_point;

      switch (origin)
      {
        case SeekOrigin.Begin:
          ref_point = 0;
          break;
        case SeekOrigin.Current:
          ref_point = position;
          break;
        case SeekOrigin.End:
          ref_point = length;
          break;
        default:
          throw new ArgumentException("Invalid SeekOrigin");
      }

      if ((ref_point + offset < 0) || (offset > real_length))
        throw new ArgumentOutOfRangeException();

      position = ref_point + (int)offset;

      return position;
    }

    Chunk FindChunk(int offset)
    {
      return (Chunk)chunks[offset / chunk_size];
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
      int old_count = count;

      while (count > 0)
      {
        Chunk chunk = FindChunk(position);
        int coffset = position - chunk.Offset;
        int rest = chunk.Length - coffset;
        int size = System.Math.Min(count, rest);

        Array.Copy(chunk.Buffer, coffset, buffer, offset, size);
        position += size;
        offset += size;
        count -= size;
      }

      return old_count;
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
      if (position + count > length)
        SetLength(position + count);

      while (count > 0)
      {
        Chunk chunk = FindChunk(position);
        int coffset = position - chunk.Offset;
        int rest = chunk.Length - coffset;
        int size = System.Math.Min(count, rest);

        Array.Copy(buffer, offset, chunk.Buffer, coffset, size);
        position += size;
        offset += size;
        count -= size;
      }
    }

    public byte[] GetContents()
    {
      byte[] retval = new byte[length];
      position = 0;
      Read(retval, 0, length);
      return retval;
    }
    #endregion
  }
  #endregion
  #region DebuggerSupport
  //public class DebuggerSupport
  //{
  //  #region Delegates
  //  public delegate Type GetTypeFunc(Assembly assembly, int token);
  //  public delegate int GetMethodTokenFunc(Assembly assembly, MethodBase method);
  //  public delegate MethodBase GetMethodFunc(Assembly assembly, int token);
  //  public delegate Type GetLocalTypeFromSignatureFunc(Assembly assembly, byte[] sig);
  //  #endregion
  //  #region Statics
  //  public static GetTypeFunc get_type;
  //  public static GetMethodTokenFunc get_method_token;
  //  public static GetMethodFunc get_method;
  //  public static GetLocalTypeFromSignatureFunc local_type_from_sig;

  //  public static Delegate create_delegate(Type delegate_type, string name)
  //  {
  //    Type type = typeof(Assembly);

  //    MethodInfo mi = type.GetMethod(name, BindingFlags.Static |
  //            BindingFlags.NonPublic);
  //    if (mi == null)
  //      throw new Exception("Can't find " + name);

  //    return Delegate.CreateDelegate(delegate_type, mi);
  //  }

  //  static DebuggerSupport()
  //  {
  //    get_type = (GetTypeFunc)create_delegate(
  //      typeof(GetTypeFunc), "MonoDebugger_GetType");

  //    get_method_token = (GetMethodTokenFunc)create_delegate(
  //      typeof(GetMethodTokenFunc), "MonoDebugger_GetMethodToken");

  //    get_method = (GetMethodFunc)create_delegate(
  //      typeof(GetMethodFunc), "MonoDebugger_GetMethod");

  //    local_type_from_sig = (GetLocalTypeFromSignatureFunc)create_delegate(
  //      typeof(GetLocalTypeFromSignatureFunc),
  //      "MonoDebugger_GetLocalTypeFromSignature");
  //  }

  //  public static Type GetType(Assembly assembly, int token)
  //  {
  //    return get_type(assembly, token);
  //  }

  //  public static int GetMethodToken(MethodBase method)
  //  {
  //    return get_method_token(method.ReflectedType.Assembly, method);
  //  }

  //  public static MethodBase GetMethod(Assembly assembly, int token)
  //  {
  //    return get_method(assembly, token);
  //  }

  //  public static Type GetLocalTypeFromSignature(Assembly assembly, byte[] sig)
  //  {
  //    return local_type_from_sig(assembly, sig);
  //  }
  //  #endregion
  //}
  #endregion
  #region SymbolFile
  public class SymbolFile : IDisposable
  {
    #region Statics
    public static Encoding enc = Encoding.UTF8;
    public static SymbolFile ReadSymbolFile(Assembly assembly,string name)
    {
      return assembly!=null? ReadSymbolFile(assembly,assembly.GetManifestResourceStream(name)):null;
    }
    public static SymbolFile ReadSymbolFile(Assembly assembly, Stream s)
    {
      return s != null ? new SymbolFile(assembly, s) : null;
    }
    #endregion
    #region Fields
    protected ArrayList methods = new ArrayList();
    protected ArrayList sources = new ArrayList();
    protected Hashtable method_source_hash = new Hashtable();
    protected Hashtable type_hash = new Hashtable();

    protected OffsetTable ot;
    protected int last_type_index;
    protected int last_method_index;
    protected int last_source_index;
    protected int last_namespace_index;

    protected byte[] stringBuffer;
    protected int maxCharsPerRound;

    protected Assembly assembly;
    protected BinaryReader reader;
    protected Hashtable method_hash;
    protected Hashtable source_file_hash;

    protected Hashtable method_token_hash;
    protected Hashtable source_name_hash;

    protected int lineNumberCount = 0;
    protected int localCount = 0;
    protected int stringSize = 0;

    #endregion
    #region Properties
    public virtual Assembly Assembly
    {
      get { return assembly; }
    }

    public virtual int SourceCount
    {
      get { return ot.SourceCount; }
    }

    public virtual int MethodCount
    {
      get { return ot.MethodCount; }
    }

    public virtual int TypeCount
    {
      get { return ot.TypeCount; }
    }

    public virtual int NamespaceCount
    {
      get { return last_namespace_index; }
    }

    public virtual SourceFileEntry[] Sources
    {
      get
      {
        if (reader == null)
          throw new InvalidOperationException();

        SourceFileEntry[] retval = new SourceFileEntry[SourceCount];
        for (int i = 0; i < SourceCount; i++)
          retval[i] = GetSourceFile(i + 1);
        return retval;
      }
    }

    public virtual MethodEntry[] Methods
    {
      get
      {
        if (reader == null)
          throw new InvalidOperationException();

        MethodEntry[] retval = new MethodEntry[MethodCount];
        for (int i = 0; i < MethodCount; i++)
          retval[i] = GetMethod(i + 1);
        return retval;
      }
    }

    protected internal virtual int LineNumberCount
    {
      get
      {
        return this.lineNumberCount;
      }
      set
      {
        this.lineNumberCount = value;
      }
    }
    protected internal virtual int LocalCount
    {
      get
      {
        return this.localCount;
      }
      set
      {
        this.localCount = value;
      }
    }

    protected internal BinaryReader BinaryReader
    {
      get
      {
        if (reader == null)
          throw new InvalidOperationException();

        return reader;
      }
    }

    #endregion
    #region Constructors
    public SymbolFile(){ }
    public SymbolFile(Assembly assembly, Stream stream)
    {
      this.assembly = assembly;

      reader = new BinaryReader(stream);

      try
      {
        long magic = reader.ReadInt64();
        long version = reader.ReadInt32();
        if ((magic != OffsetTable.Magic) || (version != OffsetTable.Version))
        {
          throw new SymbolFileException();
        }
        else
        {
          ot = new OffsetTable(reader);
        }
      }
      catch
      {
        throw new SymbolFileException();
      }

      method_hash = new Hashtable();
      source_file_hash = new Hashtable();
    }
    #endregion
    #region Methods
    public virtual byte[] CreateSymbolFile()
    {
      if (reader != null)
      {
        throw new InvalidOperationException();
      }
      else
      {

        using (ChunkMemoryStream stream = new ChunkMemoryStream())
        {
          this.Write(new BinaryWriter(stream));
          return stream.GetContents();
        }
      }
    }
    public virtual SourceFileEntry GetSourceFile(int index)
    {
      if ((index < 1) || (index > ot.SourceCount))
      {
        throw new ArgumentException();
      }
      if (reader == null)
      {
        throw new InvalidOperationException();
      }

      SourceFileEntry source = (SourceFileEntry)source_file_hash[index];
      if (source != null)
      {
        return source;
      }
      else
      {
        reader.BaseStream.Position 
          = ot.SourceTableOffset + SourceFileEntry.Size * (index - 1);
        source = new SourceFileEntry(this, reader);
        source_file_hash.Add(index, source);
        return source;
      }
    }
    public virtual MethodIndexEntry GetMethodIndexEntry(int index)
    {
      int old_pos = (int)reader.BaseStream.Position;
      reader.BaseStream.Position 
        = ot.MethodTableOffset + MethodIndexEntry.Size * (index - 1);
      MethodIndexEntry ie = new MethodIndexEntry(reader);
      reader.BaseStream.Position = old_pos;
      return ie;
    }

    public virtual MethodEntry GetMethodByToken(int token)
    {
      if (reader == null)
      {
        throw new InvalidOperationException();
      }
      if (method_token_hash == null)
      {
        method_token_hash = new Hashtable();

        for (int i = 0; i < MethodCount; i++)
        {
          MethodIndexEntry ie = GetMethodIndexEntry(i + 1);

          method_token_hash.Add(ie.Token, i + 1);
        }
      }

      object value = method_token_hash[token];

      return value!=null ? GetMethod((int)value) : null;
    }

    public virtual MethodEntry GetMethod(MethodBase method)
    {
      if (reader == null)
      {
        throw new InvalidOperationException();
      }
      return GetMethodByToken(method.MetadataToken);
    }

    public virtual MethodEntry GetMethod(int index)
    {
      if ((index < 1) || (index > ot.MethodCount))
      {
        throw new ArgumentException();
      }
      if (reader == null)
      {
        throw new InvalidOperationException();
      }

      MethodEntry entry = (MethodEntry)method_hash[index];
      if (entry != null)
      {
        return entry;
      }
      else
      {
        MethodIndexEntry ie = GetMethodIndexEntry(index);
        reader.BaseStream.Position = ie.FileOffset;

        entry = new MethodEntry(this, reader, index);
        method_hash.Add(index, entry);
        return entry;
      }
    }

    public virtual MethodSourceEntry GetMethodSource(int index)
    {
      if ((index < 1) || (index > ot.MethodCount))
      {
        throw new ArgumentException();
      }
      if (reader == null)
      {
        throw new InvalidOperationException();
      }

      object entry = method_source_hash[index];
      if (entry != null)
      {
        return (MethodSourceEntry)entry;
      }
      else
      {
        MethodEntry method = GetMethod(index);
        foreach (MethodSourceEntry source in method.SourceFile.Methods)
        {
          if (source.Index == index)
          {
            method_source_hash.Add(index, source);
            return source;
          }
        }
        throw new SymbolFileException("Internal error.");
      }
    }

    public virtual int FindSource(string file_name)
    {
      if (reader == null)
      {
        throw new InvalidOperationException();
      }
      if (source_name_hash == null)
      {
        source_name_hash = new Hashtable();

        for (int i = 0; i < ot.SourceCount; i++)
        {
          SourceFileEntry source = GetSourceFile(i + 1);

          source_name_hash.Add(source.FileName, i);
        }
      }

      object value = source_name_hash[file_name];
      return value == null? -1: (int)value;
    }

    public virtual void Dispose()
    {
      Dispose(true);
    }

    protected virtual void Dispose(bool disposing)
    {
      if (disposing)
      {
        if (reader != null)
        {
          reader.Close();
          reader = null;
        }
      }
    }

    protected internal virtual int AddSource(SourceFileEntry source)
    {
      sources.Add(source);
      return ++last_source_index;
    }

    protected internal virtual int DefineType(Type type)
    {
      if (type_hash.Contains(type))
      {
        return (int)type_hash[type];
      }
      else
      {
        int index = ++last_type_index;
        type_hash.Add(type, index);
        return index;
      }
    }

    protected internal virtual void AddMethod(MethodEntry entry)
    {
      methods.Add(entry);
    }

    protected internal virtual int GetNextTypeIndex()
    {
      return ++last_type_index;
    }

    protected internal virtual int GetNextMethodIndex()
    {
      return ++last_method_index;
    }

    protected internal virtual int GetNextNamespaceIndex()
    {
      return ++last_namespace_index;
    }

    protected internal virtual void WriteString(BinaryWriter bw, string s)
    {
      int len = enc.GetByteCount(s);
      bw.Write(len);
      stringSize += len;

      if (stringBuffer == null)
      {
        stringBuffer = new byte[512];
        maxCharsPerRound = 512 / enc.GetMaxByteCount(1);
      }

      int chpos = 0;
      int chrem = s.Length;
      while (chrem > 0)
      {
        int cch = (chrem > maxCharsPerRound) ? maxCharsPerRound : chrem;
        int blen = enc.GetBytes(s, chpos, cch, stringBuffer, 0);
        bw.Write(stringBuffer, 0, blen);

        chpos += cch;
        chrem -= cch;
      }
    }

    protected internal virtual string ReadString(int offset)
    {
      int old_pos = (int)reader.BaseStream.Position;
      reader.BaseStream.Position = offset;

      string text = ReadString();

      reader.BaseStream.Position = old_pos;
      return text;
    }

    protected internal virtual string ReadString()
    {
      int length = reader.ReadInt32();
      byte[] data = reader.ReadBytes(length);
      return enc.GetString(data);
    }

    protected internal virtual void Write(BinaryWriter bw)
    {
      // Magic number and file version.
      bw.Write(OffsetTable.Magic);
      bw.Write(OffsetTable.Version);

      //
      // Offsets of file sections; we must write this after we're done
      // writing the whole file, so we just reserve the space for it here.
      //
      long offset_table_offset = bw.BaseStream.Position;

      ot.Write(bw);

      //
      // Write data sections.
      //
      ot.DataSectionOffset = (int)bw.BaseStream.Position;
      foreach (SourceFileEntry source in sources)
      {
        source.WriteData(bw);
      }
      ot.DataSectionSize = (int)bw.BaseStream.Position - ot.DataSectionOffset;

      //
      // Write method table.
      //
      ot.MethodTableOffset = (int)bw.BaseStream.Position;
      for (int i = 0; i < methods.Count; i++)
      {
        MethodEntry entry = (MethodEntry)methods[i];
        entry.WriteIndex(bw);
      }
      ot.MethodTableSize = (int)bw.BaseStream.Position - ot.MethodTableOffset;

      //
      // Write source table.
      //
      ot.SourceTableOffset = (int)bw.BaseStream.Position;
      for (int i = 0; i < sources.Count; i++)
      {
        SourceFileEntry source = (SourceFileEntry)sources[i];
        source.Write(bw);
      }
      ot.SourceTableSize = (int)bw.BaseStream.Position - ot.SourceTableOffset;

      //
      // Fixup offset table.
      //
      ot.TypeCount = last_type_index;
      ot.MethodCount = methods.Count;
      ot.SourceCount = sources.Count;

      //
      // Write offset table.
      //
      ot.TotalFileSize = (int)bw.BaseStream.Position;
      bw.Seek((int)offset_table_offset, SeekOrigin.Begin);
      ot.Write(bw);
      bw.Seek(0, SeekOrigin.End);
    }
    #endregion
  }
  #endregion
}
